package com.sl.dev.modular.job.service.impl;/*
 * Copyright [2022] [https://www.xiaonuo.vip]
 *
 * Snowy采用APACHE LICENSE 2.0开源协议，您在使用过程中，需要注意以下几点：
 *
 * 1.请不要删除和修改根目录下的LICENSE文件。
 * 2.请不要删除和修改Snowy源码头部的版权声明。
 * 3.本项目代码可免费商业使用，商业使用请保留源码和相关描述文件的项目出处，作者声明等。
 * 4.分发源码时候，请注明软件出处 https://www.xiaonuo.vip
 * 5.不可二次分发开源参与同类竞品，如有想法可联系团队xiaonuobase@qq.com商议合作。
 * 6.若您的项目无法满足以上几点，需要更多功能代码，获取Snowy商业授权许可，请在官网购买授权，地址为 https://www.xiaonuo.vip
 */

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.pattern.CronPattern;
import cn.hutool.extra.spring.SpringUtil;
import com.sl.biz.api.JobApi;
import com.sl.biz.domain.request.JobAddRpcRequest;
import com.sl.biz.domain.request.JobPageParamRpcRequest;
import com.sl.biz.domain.request.JobUpdateByIdRpcRequest;
import com.sl.biz.domain.response.JobVO;
import com.sl.common.exception.CommonException;
import com.sl.common.page.PageList;
import com.sl.common.timer.CommonTimerTaskRunner;
import com.sl.dev.modular.job.entity.DevJob;
import com.sl.dev.modular.job.enums.DevJobCategoryEnum;
import com.sl.dev.modular.job.enums.DevJobStatusEnum;
import com.sl.dev.modular.job.param.*;
import com.sl.dev.modular.job.service.DevJobService;
import lombok.RequiredArgsConstructor;
import org.springframework.aop.support.AopUtils;
import org.springframework.scheduling.support.CronExpression;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 定时任务Service接口实现类
 *
 * @author xuyuxiang
 * @date 2022/8/5 10:47
 **/
@Service
@RequiredArgsConstructor
public class DevJobServiceImpl implements DevJobService {

    private final JobApi jobApi;

    @Override
    public List<DevJob> page(DevJobPageParam devJobPageParam) {
        JobPageParamRpcRequest jobPageParamRpcRequest = BeanUtil.toBean(devJobPageParam, JobPageParamRpcRequest.class);
        jobPageParamRpcRequest.setGroupCode(devJobPageParam.getCategory());
        jobPageParamRpcRequest.setPageIs(true);
        PageList<JobVO> page = jobApi.page(jobPageParamRpcRequest);

        return new PageList<>(page, DevJob.class, (t) -> {
            DevJob devJob = new DevJob();
            devJob.setId(t.getId());
            devJob.setName(t.getJobName());
            devJob.setCode(t.getJobCode());
            devJob.setCategory(t.getGroupCode());
            devJob.setActionClass(t.getBeanName());
            devJob.setCronExpression(t.getCron());
            devJob.setJobStatus(t.getState());
            return devJob;
        });
    }

    @Override
    public List<DevJob> list(DevJobListParam devJobListParam) {

        JobPageParamRpcRequest jobPageParamRpcRequest = BeanUtil.toBean(devJobListParam, JobPageParamRpcRequest.class);
        jobPageParamRpcRequest.setGroupCode(devJobListParam.getCategory());
        jobPageParamRpcRequest.setPageIs(false);
        PageList<JobVO> page = jobApi.page(jobPageParamRpcRequest);

        return PageList.toList(page, DevJob.class, (t) -> {
            DevJob devJob = new DevJob();
            devJob.setId(t.getId());
            devJob.setName(t.getJobName());
            devJob.setCode(t.getJobCode());
            devJob.setCategory(t.getGroupCode());
            devJob.setActionClass(t.getBeanName());
            devJob.setCronExpression(t.getCron());
            devJob.setJobStatus(t.getState());
            return devJob;
        });

    }

    @Override
    public String add(DevJobAddParam devJobAddParam) {
        checkParam(devJobAddParam);

        DevJob devJob = BeanUtil.toBean(devJobAddParam, DevJob.class);
        devJob.setCode(RandomUtil.randomString(10));
        devJob.setJobStatus(DevJobStatusEnum.STOPPED.getValue());

        JobAddRpcRequest jobAddRpcRequest = BeanUtil.toBean(devJob, JobAddRpcRequest.class);
        jobAddRpcRequest.setGroupCode(devJob.getCategory());
        jobAddRpcRequest.setBeanName(devJob.getActionClass());
        jobAddRpcRequest.setJobCode(devJob.getCode());
        jobAddRpcRequest.setJobName(devJob.getName());
        jobAddRpcRequest.setCron(devJob.getCronExpression());
        return jobApi.save(jobAddRpcRequest);
    }

    private void checkParam(DevJobAddParam devJobAddParam) {
        DevJobCategoryEnum.validate(devJobAddParam.getCategory());
        try {
            CronPattern.of(devJobAddParam.getCronExpression());
        } catch (Exception e) {
            throw new CommonException("cron表达式：{}格式不正确", devJobAddParam.getCronExpression());
        }
        try {
            Class<?> actionClass = Class.forName(devJobAddParam.getActionClass());
            if (!CommonTimerTaskRunner.class.isAssignableFrom(actionClass)) {
                List<String> actionClassArr = StrUtil.split(devJobAddParam.getActionClass(), StrUtil.DOT);
                throw new CommonException("定时任务对应的类：{}不符合要求", actionClassArr.get(actionClassArr.size() - 1));
            }
        } catch (ClassNotFoundException e) {
            throw new CommonException("定时任务找不到对应的类，名称为：{}", devJobAddParam.getActionClass());
        }
        boolean hasSameJob = jobApi.count(devJobAddParam.getActionClass(), devJobAddParam.getCronExpression());

        if (hasSameJob) {
            throw new CommonException("存在重复执行的定时任务，名称为：{}", devJobAddParam.getName());
        }
    }

    @Override
    public void edit(DevJobEditParam devJobEditParam) {
        DevJob devJob = this.queryEntity(devJobEditParam.getId());
        if (devJob.getJobStatus().equals(DevJobStatusEnum.RUNNING.getValue())) {
            throw new CommonException("运行中的定时任务不可编辑，id值为：{}", devJob.getId());
        }
        checkParam(devJobEditParam);
        BeanUtil.copyProperties(devJobEditParam, devJob);

        JobUpdateByIdRpcRequest jobUpdateByIdRpcRequest = new JobUpdateByIdRpcRequest();
        jobUpdateByIdRpcRequest.setId(devJob.getId());
        jobUpdateByIdRpcRequest.setGroupCode(devJob.getCategory());
        jobUpdateByIdRpcRequest.setBeanName(devJob.getName());
        jobUpdateByIdRpcRequest.setJobCode(devJob.getCode());
        jobUpdateByIdRpcRequest.setJobName(devJob.getName());
        jobUpdateByIdRpcRequest.setCron(devJob.getCronExpression());
        jobApi.updateById(jobUpdateByIdRpcRequest);
    }

    private void checkParam(DevJobEditParam devJobEditParam) {
        DevJobCategoryEnum.validate(devJobEditParam.getCategory());
        if (!CronExpression.isValidExpression(devJobEditParam.getCronExpression())) {
            throw new CommonException("cron表达式：{}格式不正确", devJobEditParam.getCronExpression());
        }
        try {
            Class<?> actionClass = Class.forName(devJobEditParam.getActionClass());
            if (!CommonTimerTaskRunner.class.isAssignableFrom(actionClass)) {
                List<String> actionClassArr = StrUtil.split(devJobEditParam.getActionClass(), StrUtil.DOT);
                throw new CommonException("定时任务对应的类：{}不符合要求", actionClassArr.get(actionClassArr.size() - 1));
            }
        } catch (ClassNotFoundException e) {
            throw new CommonException("定时任务找不到对应的类，名称为：{}", devJobEditParam.getActionClass());
        }
        boolean hasSameJob = jobApi.count(devJobEditParam.getActionClass(), devJobEditParam.getCronExpression(),
                devJobEditParam.getId());
        if (hasSameJob) {
            throw new CommonException("存在重复的定时任务，名称为：{}", devJobEditParam.getName());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(List<DevJobIdParam> devJobIdParamList) {
        List<String> devJobIdList = CollStreamUtil.toList(devJobIdParamList, DevJobIdParam::getId);
        if (ObjectUtil.isNotEmpty(devJobIdList)) {
            // 执行删除
            jobApi.removeByIds(devJobIdList);
        }
    }

    @Override
    public DevJob detail(DevJobIdParam devJobIdParam) {
        return this.queryEntity(devJobIdParam.getId());
    }

    @Override
    public DevJob queryEntity(String id) {
        JobVO byId = jobApi.getById(id);
        if (ObjectUtil.isEmpty(byId)) {
            throw new CommonException("定时任务不存在，id值为：{}", id);
        }
        DevJob devJob = new DevJob();
        devJob.setId(byId.getId());
        devJob.setName(byId.getJobName());
        devJob.setCode(byId.getJobCode());
        devJob.setCategory(byId.getGroupCode());
        devJob.setActionClass(byId.getBeanName());
        devJob.setCronExpression(byId.getCron());
        devJob.setJobStatus(byId.getState());

        return devJob;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void stopJob(DevJobIdParam devJobIdParam) {
        DevJob devJob = this.detail(devJobIdParam);
        if (devJob.getJobStatus().equals(DevJobStatusEnum.STOPPED.getValue())) {
            throw new CommonException("定时任务已经处于停止状态，id值为：{}", devJob.getId());
        }
        // 将运行中的定时任务停止
        jobApi.stopJob(devJobIdParam.getId());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void runJob(DevJobIdParam devJobIdParam) {
        DevJob devJob = this.detail(devJobIdParam);
        if (devJob.getJobStatus().equals(DevJobStatusEnum.RUNNING.getValue())) {
            throw new CommonException("定时任务已经处于运行状态，id值为：{}", devJob.getId());
        }

        jobApi.runJob(devJobIdParam.getId());

    }

    @Override
    public void runJobNow(DevJobIdParam devJobIdParam) {
        DevJob devJob = this.detail(devJobIdParam);
        if (devJob.getJobStatus().equals(DevJobStatusEnum.STOPPED.getValue())) {
            // 如果是停止的，则先开启运行
            this.runJob(devJobIdParam);
        }
    }

    @Override
    public List<String> getActionClass() {
        Map<String, CommonTimerTaskRunner> commonTimerTaskRunnerMap = SpringUtil.getBeansOfType(CommonTimerTaskRunner.class);
        if (ObjectUtil.isNotEmpty(commonTimerTaskRunnerMap)) {
            Collection<CommonTimerTaskRunner> values = commonTimerTaskRunnerMap.values();
            return values.stream().map(commonTimerTaskRunner -> {
                Class<?> clazz = null;
                if (AopUtils.isAopProxy(commonTimerTaskRunner)) {
                    clazz = AopUtils.getTargetClass(commonTimerTaskRunner);
                } else {
                    clazz = commonTimerTaskRunner.getClass();
                }
                return clazz.getName();
            }).collect(Collectors.toList());
        } else {
            return CollectionUtil.newArrayList();
        }
    }
}
